package com.starry.core.tenant.core.security;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.starry.core.common.constants.CommonConstant;
import com.starry.core.common.domain.R;
import com.starry.core.common.tenant.context.TenantContextHolder;
import com.starry.core.common.utils.ServletUtils;
import com.starry.core.security.context.SecurityInfoContext;
import com.starry.core.security.domain.BaseUser;
import com.starry.core.tenant.config.TenantProperties;
import com.starry.core.tenant.core.service.TenantFrameworkService;
import com.starry.core.web.core.web.execption.GlobalExceptionHandler;
import com.starry.feign.constants.RpcConstants;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * 多租户 Security Web 过滤器
 * 如果是登陆的用户，校验是否有权限访问该租户，避免越权问题。
 * 如果请求未带租户的编号，检查是否是忽略的 URL，否则也不允许访问。
 * 校验租户是合法，例如说被禁用、到期
 *
 * @author xiaoke
 */
public class TenantSecurityWebFilter extends OncePerRequestFilter {

    private final TenantProperties tenantProperties;
    private final AntPathMatcher pathMatcher;
    private final TenantFrameworkService tenantFrameworkService;
    private final GlobalExceptionHandler globalExceptionHandler;

    public TenantSecurityWebFilter(TenantProperties tenantProperties, TenantFrameworkService tenantFrameworkService, GlobalExceptionHandler globalExceptionHandler) {
        this.tenantProperties = tenantProperties;
        this.tenantFrameworkService = tenantFrameworkService;
        this.globalExceptionHandler = globalExceptionHandler;
        this.pathMatcher = new AntPathMatcher();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 如果是忽略的 URL，则不进行租户的解析
        if (isIgnoreUrl(request)) {
            TenantContextHolder.setIgnore(true);
            chain.doFilter(request, response);
            return;
        }
        Long tenantId = TenantContextHolder.getTenantId();
        BaseUser loginUser = SecurityInfoContext.getLoginUser();
        if (loginUser != null) {
            if (loginUser.getTenantId() != null) {
                TenantContextHolder.setTenantId(loginUser.getTenantId());
            }
        }
        if (request.getRequestURI().startsWith(RpcConstants.RPC_API_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        // 校验租户是合法，例如说被禁用、到期（系统租户排除）
        if (tenantId != null && !Objects.equals(tenantId, CommonConstant.SYSTEM_TENANT_ID)) {
            try {
                tenantFrameworkService.validTenant(tenantId);
            } catch (Exception e) {
                R<?> result = globalExceptionHandler.allExceptionHandler(request, e);
                ServletUtils.writeJSON(response, result);
                return;
            }
        }
        // 继续过滤
        chain.doFilter(request, response);
    }

    private boolean isIgnoreUrl(HttpServletRequest request) {
        // 快速匹配，保证性能
        if (CollUtil.contains(tenantProperties.getIgnoreUrls(), request.getRequestURI())) {
            return true;
        }
        // 逐个 Ant 路径匹配
        for (String url : tenantProperties.getIgnoreUrls()) {
            if (pathMatcher.match(url, request.getRequestURI())) {
                return true;
            }
        }
        return false;
    }

    private void writeResponse(R<?> data, HttpServletResponse response) {
        try {
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.getWriter().write(JSONUtil.toJsonStr(data));
            response.getWriter().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
